/*! 
	\file nscamera.h
	
	\brief Header file for NSCamera class

	This file contains all of the neccessary declarations for the NSCamera class.

	\author Daniel Randle
	\date Mar 16 2013
	\copywrite Earth Banana Games 2013
*/

#ifndef NSCAMERA_H
#define NSCAMERA_H

 //includes
#include <nsmath3d.h>
#include <boost/timer.hpp>
#include <string>

/*! 
	NSCamera is responsible for all camera control.

	The NSCamera class creates a camera in 3d space by storing several variables and adjusting them as
	neccessary to produce two different types of transforms. When the camera is in "FREE mode" then the
	matrix transform is the translation * rotation * scale matrices multiplied together while in "FOCUS mode"
	the transform is the rotation * translation * scale matrices multiplied together. In effect, in free
	mode the camera moves as in a fps while in focus mode the camer is rotating around a single point
	which is held in the 3d vector member variable rotationPoint.

	\bug No known bugs.
*/
class NSCamera
{
public:
	/*! 
		Defines camera free or focus states
	 */
	enum CameraMode {
		FREE /*!< Free mode - camera moves as fps */,
		FOCUS /*!< Focus mode - camera focuses on single object */};

	/*! 
		Assigns integers to different travel directions

		Basically the amount to move either needs to be positive
		or negative depending on the direction - this enum takes care of that.
	*/
	enum Direction {
		FRONT = -1 /*!< Forward movement */,
		BACK = 1 /*!< Backward movement */,
		RIGHT = -1 /*!< Strafe right movement */,
		LEFT = 1 /*!< Strafe left movement */,
		UP = -1 /*!< Verticle up movement */,
		DOWN = 1 /*!< Verticle down movement */,
		ZoomIn = -1 /*!< Forward movement (same as FRONT)*/,
		ZoomOut = 1 /*!< Backward movement (same as BACK) */};

	/*!
		This enum is used to indicate whether we are interested in either the up, verticle, or normal
		vectors. It can be a function parameter type used to indicate which vector
		is of interest.
	 */
	enum UVNChoice { 
		UVector /*!< Up vector */, 
		VVector /*!< Verticle vector */, 
		NVector /*!< Normal vector */ };

	/*! 
		Enum holds 3 different pre-set camera view identifiers
	 */
	enum CameraView {
		Top /*!< Top down camera view */,
		Iso /*!< Isometric camera view */,
		Front /*!< Front on camera view */};

	/*! 
		Most arguments have default values you can use at first but you must
		specify the name of the camera (you make up the name) and which mode
		you want it to start out in

		\param camID 
			The unique name you want to give to the camera
		\param mode 
			The mode you want the camera to start in
		\param _speed 
			The speed to travel in units per second
		\param sens 
			The sensitivity of rotation when rotating the camera
		\param zoomFac 
			The amount to zoom in/out with each function call
		\param pos 
			The starting position of the camera
		\param orient 
			The starting orientation of the camera
		\param rotPoint 
			The starting point that the camera will rotate around
			in focus mode
		\param startingOrientation 
			This is a 3d matrix holding the starting
			unit vector orientations of the camera (for u, v, and n vectors)
	 */
	NSCamera(const std::string & camID, 
		CameraMode mode=FOCUS,
		float _speed=10.0f,
		float sens=0.1f,
		float zoomFac=5.0f,
		const NSVec3Df & pos=NSVec3Df(),
		const NSVec3Df & orient=NSVec3Df(180.0f,0.0f,0.0f), // we want to be looking down at the map initially
		const NSVec3Df & rotPoint=NSVec3Df(),
		const NSMatrix3Df & startingOrientation = NSMatrix3Df());

	//! Destroy the camera class
	~NSCamera();

	/*! 
		Change camera speed in units per second
		\param 
			amount The amount to change the speed by (can be positive or negative)
		\return 
			void
	 */
	void changeSpeed(float amount);

	/*! 
		Change rotation sensativity - higher means the more quickly the camera 
		will rotate. Also effects the speed at which the camera will move left/right
		up/down when using the mouse for strafing control.

		\param 
			amount The amount to change the sensativity by (can be positive or negative)
		\return 
			void
	*/
	void changeSensitivity(float amount);

	/*! 
		Elevate or drop the camera vertically depending on the _upOrDown parameter

		When this function is called the camera will begin elevating or dropping
		at the speed given by getSpeed() until the function stopElevating()
		is called. This effectively provides smooth rising and falling for the camera.
		This function does nothing if the camera is already elevating.
	
		\param _upOrDown 
			Holds whether you want the camera to elevate up or down
	
		\return
			True if the camera was not currently elevating and false if it
			was already in the process of elevating
	 */
	bool elevate(Direction _upOrDown);

	/*! 
		Move the camera forward or backward depending on _frontOrBack parameter

		On calling this function the camera will continue moving in the _frontOrBack
		direction until stopFlying() is called. This function does nothing if the
		camera is already flying.
	
		\param _frontOrBack 
			Holds whether you want the camera to elevate up or down
	
		\return
			True if the camera was not currently flying (moving forward or backward)
			and false if it was already in the process of flying.
	 */
	bool fly(Direction _frontOrBack);

	/*! 
		Returns the 4 dimensional transformation matrix neccessary to transform
		a point in 3d space to camera space. The transform that is returned is
		dependent on the camera mode.
	
		\param
			NONE
	
		\return
			4 dimensional matrix that is dependent on camera mode.
			For free mode : position * rotation * scaling is returned.
			For focus mode : rotation * position * scaling is returned.
	 */
	NSMatrix4Df getTransform();

	/*!
		Returns the 4 dimensional transformation matrix that can be used to transform
		a point in 3d space to have the rotation in camera space.
	
		\param
			NONE
	
		\return
			4 dimensional rotation matrix
	 */
	NSMatrix4Df getRotationTransform();

	/*! 
		Get the current camera mode.

		\param 
			NONE
		\return 
			The camera mode as a type CameraMode
	*/
	CameraMode getCameraMode() const;

	/*! 
		Return the orientation of the camera as angles about each axis.

		\param 
			NONE
		\return 
			3d vector storing the angles about each axis (x, y, and z).
	*/
	NSVec3Df getOrientation() const;

	/*! 
		Get the camera's position either relative to the rotationPoint if in focus mode
		or in 3d space if in free mode.

		\param 
			NONE
		\return 
			3d vector storing the position of the camera.
			If in Free camera mode then the x,y,z coordinates are relative to the origin.
			If in Focus camera mode then the x,y,z coordinates are relative to the
			rotationPoint value.
	*/
	NSVec3Df getPosition(CameraMode which);

	/*! 
		Get the current speed of the camera in units per second.

		\param 
			NONE
		\return 
			A floating point number representing the camera's speed.
	*/
	float getSpeed() const;

	/*! 
		Get either the up vector, verticle vector, or the normal vector representing the
		camera's orientation in 3d space.

		\param 
			which Specify which vector you wish to have returned.
		\return 
			Either up, verticle, or normal vector.
	*/
	NSVec3Df getUVNVec(UVNChoice which) const;

	/*! 
		Moves the camera based one the change in mouse movement across the screen.
		This move is reliant on the value of sensitivity for how fast the camera
		will strafe left/right and up/down. The more sensitive the faster the camera
		will move.

		\param 
			xMouseChange The amount the cursor has moved in the x direction.
		\param 
			yMouseChange The amount the cursor has moved in the y direction.
		\return 
			void
	*/
	void move(int xMouseChange, int yMouseChange);

	/*! 
		This function rotates the camera by 90 degrees around the rotationPoint.

		\param 
			NONE
		\return 
			void
	*/
	void quickRotate();

	/*! 
		Rotate the camera around the axis according to the function parameters.

		\param 
			xAng The amount to rotate around the x axis.
		\param
			yAng The amount to rotate around the y axis.
		\param
			zAng the amount to rotate around the z axis.
		\return 
			void
	*/
	void rotate(float xAng,float yAng, float zAng);

	/*! 
		Overloaded function - provide 3d vector instead of 3 individual values.

		\param 
			toRotate Vector providing the wanted rotation about each axis.
		\return 
			void
	*/
	void rotate(const NSVec3Df & toRotate);

	/*! 
		Set the speed of the camera.

		\param 
			unitPerSecond Self explanitory
		\return 
			void
	*/
	void setSpeed(float unitPerSecond);

	/*! 
		Set the sensitivity of the camera. This value effects automatic camera strafing
		and rotation based on mouse movement.

		\param 
			unitPerSecond Self explanitory
		\return 
			void
	*/
	void setSensitivity(float sens);

	/*! 
		Sets the rotation point that the camera will rotate around in focus mode. Call this
		function using the center point of an object you wish the camera to be focused on.
		Only effects the camera in focus mode.

		\param 
			point The point used to set the camera's rotation point.
		\return 
			void
	*/
	void setRotationPoint(const NSVec3Df & point);

	/*! 
		Sets the position of the camera relative to the origin if in free mode
		and relative to the rotationPoint if in focus mode.

		\param 
			point The point used to set the camera's position.
		\return 
			void
	*/
	void setPosition(const NSVec3Df & point);

	/*! 
		This will give the camera a new orientation by setting it, not adding it
		to the previous orientation. So if the camera is rotated 45 degrees about
		each axis and this function is called with each value in the vector set to
		35 degrees, the new camera orientation will be 30 degrees about each axis
		not 80 degrees.

		In free mode the rotation is about the origin (and the axis) while in focus mode
		it is about the rotationPoint.

		\param 
			orient The 3d vector holding the rotations about each axis.
		\return 
			void
	*/
	void setOrientation(const NSVec3Df & orient);

	/*! 
		Sets the camera to either free or focus mode.

		\param 
			mode The mode the camera will be set to.
		\return 
			void
	*/
	void setCameraMode(CameraMode mode);

	/*! 
		Sets the timer of the camera. This function must be called when
		a new camera object is initialized as the timer is used in every
		frame for smooth movement when using the fly(), elevate(), or strafe()
		functions.

		\param 
			_timer A boost library timer that should be started during the initialization
			process.
		\return 
			void
	*/
	void setTimer(boost::timer * _timer);

	/*! 
		Set the camera to one of the preset camera views given by the CameraView enum.

		\param 
			view The preset view to change the camera to.
		\return 
			void
	*/
	void setView(CameraView view);

	/*! 
		Stops the camera from any further elevation/dropping. Use after calling
		elevate to make the camera stop.

		\param 
			NONE
		\return 
			True if camera was elevating and the function stopped it, false if
			the camera was not elevating at the time of the function call.
	*/
	bool stopElevating();

	/*! 
		Stops the camera from any further flying forward/backward. Use after calling
		fly to make the camera stop.

		\param 
			NONE
		\return 
			True if camera was flying and the function stopped it, false if
			the camera was not flying at the time of the function call.
	*/
	bool stopFlying();

	/*! 
		Stops the camera from any further strafing. Use after calling
		strafe to make the camera stop.

		\param 
			NONE
		\return 
			True if camera was strafing and the function stopped it, false if
			the camera was not strafing at the time of the function call.
	*/
	bool stopStrafing();

	/*! 
		Stops the camera from any further strafing. Use after calling
		strafe to make the camera stop.

		\param 
			NONE
		\return 
			True if camera was strafing and the function stopped it, false if
			the camera was not strafing at the time of the function call.
	*/
	bool strafe(Direction _leftOrRight);

	/*! 
		Toggles the camera from free to focus mode or from focus mode to
		free mode.

		\param 
			NONE
		\return 
			void
	*/
	void toggleCamera();

	/*! 
		Toggles the camera through preset views, the order is always Top to Iso to Front
		and then back around to Top.

		\param 
			NONE
		\return 
			void
	*/
	void toggleView();

	/*! 
		Moves the camera instantly in the x, y, and z directions given by the parameters.

		\param 
			xTrans, yTrans, zTrans The amount to translate the camera in each direction.
		\return 
			void
	*/
	void translate(float xTrans, float yTrans, float zTrans);

	/*! 
		Overloaded function : send a 3d vector containing the directions rather than
		3 individual values.

		\param 
			amount The 3d vector containing the amount to translate the camera in each
			direction.
		\return 
			void
	*/
	void translate(const NSVec3Df & amount);

	/*! 
		Rotate the camera based on a change in cursor position. The amount to rotate is
		effected by the sensativity.

		\param 
			xMouseChange The amount the cursor has moved in the x direction.
		\param
			yMouseChange The amount the cursor has moved in the y direction.
		\return 
			void
	*/
	void turn(int xMouseChange, int yMouseChange);

	/*! 
		Call this function every time the game is updated - it moves the camera
		as needed based on the speed and sensitivity values and the time elapsed
		since the last update.

		The elapsed time is pulled from the timer here so it is important that you
		call the update every time the game calls it or you will get very jerky
		movements (from large elapsed times).

		\param 
			NONE
		\return 
			void
	*/
	void update();

	/*! 
		Moves the camera in the z direction, which if in focus mode, is towards the
		rotationPoint. This can also be called zooming. The zoomFactor determines how
		much the camera moves on each function call.

		This movement is meant to look more choppy if called repeatedly. Use fly if you want
		smooth z movement - this function is mainly so you can assign you mouse wheel to call
		it on every click as most 3d programs will do.

		\param 
			inOrOut Specify whether the camera should zoom in or out.
		\return 
			void
	*/
	void zoom(Direction inOrOut);

private:
	void _recalcUVNMatrix();	// used to store temp matrix for recalculation
	NSVec3Df position;          // camera position relative to origin or rotationPoint
	NSVec3Df rotationPoint;		// point used for focus mode movements and rotations
	NSVec3Df orientation;		// orientation of the camera relative to origin or rotationPoint
	NSMatrix3Df uvnMatrix;		// matrix holding the camera orientation in uvn form
	NSMatrix3Df startingOrient;	// starting orientation of the uvn matrix

	CameraMode currentMode;		// current mode of camera, free or focus
	CameraView cameraView;		// current preset view

	Direction frontOrBack;		// which direction the camera is currently traveling
	Direction leftOrRight;		// if any of these are non-zero it means the camera is
	Direction upOrDown;			// moving in a direction

	bool flying;				// is the camera flying
	bool strafing;				// is the camera strafing
	bool elevating;				// is the camera elevating

	std::string ID;				// string unique ID given to camera

	float speed;				// speed in units per second used in animation
	float zoomFactor;			// how far to zoom in each function call
	float sensitivity;			// sensitivity for mouse movement operations
	float lastTime;				// the time of the last call to update

	boost::timer * timer;		// The main animation timer
};

#endif